﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using LINQPad.Extensibility.DataContext;
using System.Windows.Forms;

namespace LinqPadSP2010ContextDriver.StaticDriver
{
	/// <summary>
	/// This static driver let users query any data source that looks like a Data Context - in other words,
	/// that exposes properties of type IEnumerable of T.
	/// </summary>
	public class SharePointDriver : StaticDataContextDriver
	{
		public override string Name { get { return "Static SharePoint 2010 Driver"; } }

		public override string Author { get { return "Marcus Peters"; } }	

		public override string GetConnectionDescription (IConnectionInfo cxInfo)
		{
			// For static drivers, we can use the description of the custom type & its assembly:
            //return cxInfo.CustomTypeInfo.GetCustomTypeDescription();
            return cxInfo.DriverData.Element("SPUrl").Value;
		}

        public override object[] GetContextConstructorArguments(IConnectionInfo cxInfo)
        {

            return new object[] {cxInfo.DriverData.Element("SPUrl").Value};                  
        }

        public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo)
        {
            return new ParameterDescriptor[] { new ParameterDescriptor("requestUrl", "System.String") };
        }
        
        
        public override bool ShowConnectionDialog (IConnectionInfo cxInfo, bool isNewConnection)
		{
			// Prompt the user for a custom assembly and type name:
			return new ConnectionDialog (cxInfo).ShowDialog() == true;
		}

        public override void InitializeContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
        {
            ((Microsoft.SharePoint.Linq.DataContext)context).Log = executionManager.SqlTranslationWriter;
        }
        

        public override IEnumerable<string> GetAssembliesToAdd()
        {
            string spdllHive = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), @"Microsoft Shared\Web Server Extensions\14\ISAPI");         
            return new[] {Path.Combine(spdllHive, "Microsoft.SharePoint.dll"),Path.Combine(spdllHive, "Microsoft.SharePoint.Linq.dll")};
        }


        public override void TearDownContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager, object[] constructorArguments)
        {
            ((IDisposable)context).Dispose(); 
            
        }
        
        public override List<ExplorerItem> GetSchema (IConnectionInfo cxInfo, Type customType)
		{
			// Return the objects with which to populate the Schema Explorer by reflecting over customType.
			
			// We'll start by retrieving all the properties of the custom type that implement IEnumerable<T>:
			var topLevelProps =
			(
				from prop in customType.GetProperties ()
				//where prop.PropertyType != typeof (string)
                where prop.PropertyType.ToString().StartsWith("Microsoft.SharePoint.Linq.EntityList`1")                  

				
                // Display all properties of type IEnumerable<T> which are of EntitiList Type
				let ienumerableOfT = prop.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1")
                where (ienumerableOfT != null) 

				orderby prop.Name

				select new ExplorerItem (prop.Name, ExplorerItemKind.QueryableObject, ExplorerIcon.Table)
				{
					IsEnumerable = true,
					ToolTipText = FormatTypeName (prop.PropertyType, false),

					// Store the entity type to the Tag property. We'll use it later.
					Tag = ienumerableOfT.GetGenericArguments () [0]
                    
				}

			).ToList ();

			// Create a lookup keying each element type to the properties of that type. This will allow
			// us to build hyperlink targets allowing the user to click between associations:
			var elementTypeLookup = topLevelProps.ToLookup (tp => (Type)tp.Tag);

			// Populate the columns (properties) of each entity:
			foreach (ExplorerItem table in topLevelProps)
				table.Children = ((Type)table.Tag)
					.GetProperties ()
					.Select (childProp => GetChildItem (elementTypeLookup, childProp))
					.OrderBy (childItem => childItem.Kind)
					.ToList ();

			return topLevelProps;
		}

		ExplorerItem GetChildItem (ILookup<Type, ExplorerItem> elementTypeLookup, PropertyInfo childProp)
		{
			// If the property's type is in our list of entities, then it's a Many:1 (or 1:1) reference.
			// We'll assume it's a Many:1 (we can't reliably identify 1:1s purely from reflection).
			if (elementTypeLookup.Contains (childProp.PropertyType))
				return new ExplorerItem (childProp.Name, ExplorerItemKind.ReferenceLink, ExplorerIcon.ManyToOne)
				{
					HyperlinkTarget = elementTypeLookup [childProp.PropertyType].First (),
					// FormatTypeName is a helper method that returns a nicely formatted type name.
					ToolTipText = FormatTypeName (childProp.PropertyType, true)
				};

			// Is the property's type a collection of entities?
			Type ienumerableOfT = childProp.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1");
			if (ienumerableOfT != null)
			{
				Type elementType = ienumerableOfT.GetGenericArguments () [0];
				if (elementTypeLookup.Contains (elementType))
					return new ExplorerItem (childProp.Name, ExplorerItemKind.CollectionLink, ExplorerIcon.OneToMany)
					{
						HyperlinkTarget = elementTypeLookup [elementType].First (),
						ToolTipText = FormatTypeName (elementType, true)
					};
			}

			// Ordinary property:
			return new ExplorerItem (childProp.Name + " (" + FormatTypeName (childProp.PropertyType, false) + ")",
				ExplorerItemKind.Property, ExplorerIcon.Column);
		}
	}
}
